//
//  SourceList.m
//  iLog fitness
//
//  Created by Keith Duncan on 04/05/2007.
//  Copyright 2007 BUOY. All rights reserved.
//

#import "SourceList.h"

#import "Amber/Amber.h"

#import "ShadowTextCell.h"
#import "ImageTextCell.h"
#import "DisclosureButton.h"

@interface SourceList (Private)
- (CGFloat)_heightForTreeArray:(NSArray *)array;

- (DisclosureButton *)_newDisclosureButtonForNode:(id)node;
- (DisclosureButton *)_disclosureButtonForNode:(id)node;
@end

@interface SourceList (PrivateDrawing)
- (NSSize)_resetFrameSize:(NSSize)size;
- (CGFloat)_disclosureButtonSize;
- (CGFloat)_rowIndentForLevel:(NSUInteger)level;
@end

@interface SourceList (PrivateSelection)
- (void)_discloseSelection:(NSInteger)direction;
- (void)_discloseNode:(NSTreeNode *)node;
- (void)_selectNode:(NSTreeNode *)node;
- (void)_moveSelection:(NSInteger)direction;
- (void)_scrollToSelection;
- (void)_expandNode:(NSTreeNode *)node;

- (NSMutableArray *)_expandedNodesFromTree:(NSTreeNode *)node;
- (void)_addExpandedNodes:(NSTreeNode *)node toCollection:(id)collection;
@end

#define initialDeltaY 3
#define initialDeltaX 6

NSString *KDContentImagesBinding = @"contentImageBinding";

@implementation SourceList

+ (void)initialize {
	[self exposeBinding:NSContentBinding];
	[self exposeBinding:KDContentImagesBinding];
	[self exposeBinding:NSContentValuesBinding];
	
	[self exposeBinding:AFSelectionIndexPathBinding];
}

- (id)initWithFrame:(NSRect)frame {
	[super initWithFrame:frame];
	
	_bindingInfo = [[NSMutableDictionary alloc] init];
	
	_rowHeight = 20;
	_nodeSeparation = 6;
		
	_titleCell = [[ShadowTextCell alloc] init];
	
	_contentsCell = [[ImageTextCell alloc] init];
	[_contentsCell setShadowColor:[NSColor blackColor]];
	
	_expanded = [[NSMutableSet alloc] init];
	_expandable = [[NSMutableSet alloc] init];
	_disclosureButtons = [[NSMutableSet alloc] init];
	
	_disclosureLevels = [[NSCountedSet alloc] init];
	
    return self;
}

- (void)dealloc {
	for (NSString *currentBinding in [_bindingInfo allKeys]) [self unbind:currentBinding];
	
	[_bindingInfo release];
	
	[_titleCell release];
	[_contentsCell release];
	
	[_expanded release];
	[_expandable release];
	[_disclosureButtons release];
	
	[_disclosureLevels release];
	
	[super dealloc];
}

- (void)setFrame:(NSRect)frame {
	frame.size = [self _resetFrameSize:frame.size];
	[super setFrame:frame];
}

- (void)setFrameSize:(NSSize)size {
	[super setFrameSize:[self _resetFrameSize:size]];
}

- (void)viewWillMoveToSuperview:(NSView *)view {
	[super viewWillMoveToSuperview:view];
	
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(display) name:NSWindowDidResignKeyNotification object:[view window]];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(display) name:NSWindowDidBecomeKeyNotification object:[view window]];
	
	if (view == nil) {
		[self unbind:NSContentBinding];
		[self unbind:AFSelectionIndexPathBinding];
	} else [self setFrame:_frame];
}

- (BOOL)isFlipped {
	return YES;
}

- (void)drawRect:(NSRect)rect {
	if ([[self window] isKeyWindow])
		[[NSColor colorWithCalibratedRed:(212.0/255.0) green:(218.0/255.0) blue:(227.0/255.0) alpha:1.0] set];
	else
		[[NSColor colorWithCalibratedWhite:(232.0/255.0) alpha:1.0] set];
	
	NSRectFill(rect);
	
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	NSIndexPath *selectionIndexPath = [self valueForBinding:AFSelectionIndexPathBinding];
	NSString *imageKeyPath = [[self keyPathForBinding:KDContentImagesBinding] stringByRemovingKeyPathComponentAtIndex:0], *valueKeyPath = [[self keyPathForBinding:NSContentValuesBinding] stringByRemovingKeyPathComponentAtIndex:0];
	
	NSRect rowFrame = [self bounds];
	rowFrame.origin.y = initialDeltaY;
	rowFrame.size.height = _rowHeight;
	
	CGFloat buttonSize = [self _disclosureButtonSize];
	
	NSInteger count;
	const NSRect *rects;
	[self getRectsBeingDrawn:&rects count:&count];
	
	BOOL draw = NO;
	
	NSArray *treeArray = [self _expandedNodesFromTree:content];
	for (NSTreeNode *currentNode in treeArray) {
		if ([[content childNodes] containsObject:currentNode] && [treeArray indexOfObjectIdenticalTo:currentNode] > 0) rowFrame.origin.y += _nodeSeparation;
		
		if (!draw) for (NSUInteger index = 0; index < count; index++) {
			if (NSIntersectsRect(rects[index], rowFrame)) {
				draw = YES;
				break;
			}
		}
		
		if (draw) {
			BOOL rootChild = [[content childNodes] containsObject:currentNode];
			BOOL highlighted = ([[currentNode indexPath] compare:selectionIndexPath] == NSOrderedSame);
			if (highlighted && !rootChild) {
				BOOL drawKey = ([[self window] firstResponder] == self && [[self window] isKeyWindow]);
				[[NSGradient sourceListSelectionGradientIsKey:drawKey] drawInRect:rowFrame angle:90];
				
				[(drawKey ? [NSColor colorWithCalibratedRed:(69.0/255.0) green:(128.0/255.0) blue:(200.0/255.0) alpha:1.0] : [NSColor colorWithCalibratedRed:(145.0/255.0) green:(160.0/255.0) blue:(192.0/255.0) alpha:1.0]) set];
				[NSBezierPath strokeLineFromPoint:(NSPoint){NSMinX(rowFrame), NSMinY(rowFrame)} toPoint:(NSPoint){NSMaxX(rowFrame), NSMinY(rowFrame)}];
			}
			
			NSCell *cell = (rootChild ? _titleCell : _contentsCell);
			NSString *stringValue = [[currentNode representedObject] valueForKeyPath:valueKeyPath];
			
			if (cell == _titleCell) {
				[_titleCell setStringValue:[stringValue uppercaseString]];
			} else if (cell == _contentsCell) {
				[_contentsCell setHighlighted:highlighted];				
				[_contentsCell setStringValue:stringValue];
				[_contentsCell setImage:[[currentNode representedObject] valueForKeyPath:imageKeyPath]];
			}
			
			NSRect rowRect = rowFrame;
			rowRect.origin.x = [self _rowIndentForLevel:[[currentNode indexPath] length]];
			rowRect.size.width = NSWidth(rowRect) - NSMinX(rowRect);
			
			if ([_expandable containsObject:currentNode]) {
				DisclosureButton *button = [self _disclosureButtonForNode:currentNode];
				
				[button setState:([_expanded containsObject:currentNode] ? NSOnState : NSOffState)];
				[button setRowHighlighted:highlighted];
				
				NSRect buttonFrame = NSMakeRect(NSMinX(rowRect) - (NSHeight(rowRect)/2.0), NSMinY(rowRect) + ((NSHeight(rowRect) - buttonSize)/2.0), buttonSize, buttonSize);
				
				if (cell == _titleCell) {
					buttonFrame.origin.x = NSMinX([_titleCell titleRectForBounds:rowRect]) + 2.5;
					
					rowRect.origin.x = NSMaxX(buttonFrame) + (NSHeight(rowRect)/10.0);
					rowRect.size.width -= NSWidth(buttonFrame);
				}
				
				[button setFrame:buttonFrame];
			}
			
			[cell drawWithFrame:rowRect inView:self];
		}
		
		rowFrame.origin.y += _rowHeight;
	}
}

- (BOOL)acceptsFirstResponder {
	return YES;
}

- (BOOL)becomeFirstResponder {	
	[self setNeedsDisplay:YES];
	return YES;
}

- (BOOL)resignFirstResponder {	
	[self setNeedsDisplay:YES];
	return YES;
}

- (void)mouseDown:(NSEvent *)event {
	NSPoint clickPoint = [self convertPoint:[event locationInWindow] fromView:nil];
	
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	NSMutableArray *treeArray = [self _expandedNodesFromTree:content];
	
	CGFloat height = initialDeltaY;
	for (NSTreeNode *currentNode in treeArray) {
		BOOL rootChild = [[content childNodes] containsObject:currentNode];
		if (rootChild && [[content childNodes] indexOfObjectIdenticalTo:currentNode] > 0) height += _nodeSeparation;
		
		if (clickPoint.y > height && clickPoint.y < (height + _rowHeight)) {
			if (!rootChild) [self _selectNode:currentNode];
			return;
		}
		
		height += _rowHeight;
	}
}

enum {
	UP = -1,
	DOWN = 1,
	CLOSE = 0,
	OPEN = 1
};

- (void)keyDown:(NSEvent *)event {
	if ([event modifierFlags] & NSNumericPadKeyMask) {
		NSString *keyStroke = [event charactersIgnoringModifiers];
		if ([keyStroke length] == 0 || [keyStroke length] > 1) return;
		
		switch ([keyStroke characterAtIndex:0]) {
		case NSLeftArrowFunctionKey:
			[self _discloseSelection:CLOSE];
			break;
		case NSRightArrowFunctionKey:
			[self _discloseSelection:OPEN];
			break;
		case NSUpArrowFunctionKey:
			[self _moveSelection:UP];
			break;
		case NSDownArrowFunctionKey:
			[self _moveSelection:DOWN];
			break;
		}
	} else [super keyDown:event];
}

@end

@implementation SourceList (KDKeyValueBinding)

- (id)infoForBinding:(NSString *)bindingName {
	NSDictionary *info = [_bindingInfo objectForKey:bindingName];
	return (info != nil) ? info : [super infoForBinding:bindingName];
}

- (void)setInfo:(id)info forBinding:(NSString *)binding {
	[_bindingInfo setObject:info forKey:binding];
}

static void *ContentObservationContext = (void *)1000;
static void *ContentPropertyObservationContext = (void *)1001;
static void *SelectionIndexPathObservationContext = (void *)1002;

- (void *)contextForBinding:(NSString *)binding {
	if ([binding isEqualToString:NSContentBinding]) return ContentObservationContext;
	else if ([binding isEqualToString:KDContentImagesBinding]) return ContentPropertyObservationContext;
	else if ([binding isEqualToString:NSContentValuesBinding]) return ContentPropertyObservationContext;
	else if ([binding isEqualToString:AFSelectionIndexPathBinding]) return SelectionIndexPathObservationContext;
	else return nil;
}

- (void)bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options {
	if ([self infoForBinding:binding] != nil) [self unbind:binding];
	
	void *context = [self contextForBinding:binding];
	NSDictionary *bindingInfo = [NSDictionary dictionaryWithObjectsAndKeys:
								 observable, NSObservedObjectKey,
								 [[keyPath copy] autorelease], NSObservedKeyPathKey,
								 [[options copy] autorelease], NSOptionsKey, nil];
	
	if ([binding isEqualToString:NSContentBinding]) {
		[_bindingInfo setObject:bindingInfo forKey:NSContentBinding];
		[observable addObserver:self forKeyPath:keyPath options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:context];
	} else if ([binding isEqualToString:KDContentImagesBinding]) {
		[_bindingInfo setObject:bindingInfo forKey:KDContentImagesBinding];
		[observable addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:context];
	} else if ([binding isEqualToString:NSContentValuesBinding]) {
		[_bindingInfo setObject:bindingInfo forKey:NSContentValuesBinding];
		[observable addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:context];
	} else if ([binding isEqualToString:AFSelectionIndexPathBinding]) {
		[_bindingInfo setObject:bindingInfo forKey:AFSelectionIndexPathBinding];
		[observable addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:context];
	} else [super bind:binding toObject:observable withKeyPath:keyPath options:options];
	
	[self setNeedsDisplay:YES];
}

- (void)unbind:(NSString *)binding {	
	id controller = [self controllerForBinding:binding];
	NSString *keyPath = [self keyPathForBinding:binding];
	
	if ([binding isEqualToString:NSContentBinding]) {
		[controller removeObserver:self forKeyPath:keyPath];
		[_bindingInfo removeObjectForKey:NSContentBinding];
	} else if ([binding isEqualToString:KDContentImagesBinding]) {
		[controller removeObserver:self forKeyPath:keyPath];
		[_bindingInfo removeObjectForKey:KDContentImagesBinding];
	} else if ([binding isEqualToString:NSContentValuesBinding]) {
		[controller removeObserver:self forKeyPath:keyPath];
		[_bindingInfo removeObjectForKey:NSContentValuesBinding];
	} else if ([binding isEqualToString:AFSelectionIndexPathBinding]) {
		[controller removeObserver:self forKeyPath:keyPath];
		[_bindingInfo removeObjectForKey:AFSelectionIndexPathBinding];
	} else [super unbind:binding];
	
	[self setNeedsDisplay:YES];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if (context == ContentObservationContext) {
		NSTreeNode *content = [self valueForBinding:NSContentBinding];
		NSMutableSet *treeSet = [content setFromNodeInclusive:NO];
		
		[_expanded intersectSet:treeSet];
		[_expanded addObject:content];
		[_expanded addObjectsFromArray:[content childNodes]];
		
		[_expandable intersectSet:treeSet];
		for (DisclosureButton *currentButton in [[_disclosureButtons copy] autorelease]) {
			if ([treeSet containsObject:currentButton.representedObject]) continue;
			
			[_disclosureLevels removeObject:[NSNumber numberWithUnsignedInteger:[[currentButton.representedObject indexPath] length]]];
			
			[currentButton removeFromSuperview];
			[_disclosureButtons removeObject:currentButton];
		}
				
		for (NSTreeNode *currentNode in treeSet) {
			if ([[currentNode childNodes] count] == 0 || ([datasource respondsToSelector:@selector(sourceList:shouldDrawDisclosureForItem:)] && ![datasource sourceList:self shouldDrawDisclosureForItem:[currentNode representedObject]])) continue;
			
			if (![_expandable containsObject:currentNode]) {
				[_expandable addObject:currentNode];
				
				DisclosureButton *button = [self _newDisclosureButtonForNode:currentNode];
				
				[_disclosureLevels addObject:[NSNumber numberWithUnsignedInteger:[[currentNode indexPath] length]]];
				
				[self addSubview:button];
				[_disclosureButtons addObject:button];
			}
		}
		
		[self setFrameSize:_frame.size];
	} else if (context == ContentPropertyObservationContext) {
		// Nothing, setNeedsDisplay:YES is sufficient
	} else if (context == SelectionIndexPathObservationContext) {		
		NSIndexPath *selectionIndexPath = [self valueForBinding:AFSelectionIndexPathBinding];
		
		NSTreeNode *content = [self valueForBinding:NSContentBinding];
		NSTreeNode *selectedNode = [content descendantNodeAtIndexPath:selectionIndexPath];
		
		[self _expandNode:selectedNode];
		[self setFrameSize:_frame.size];
		
		[self _scrollToSelection];
	} else [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
	
	[self setNeedsDisplay:YES];
}

@end

@implementation SourceList (Private)

- (CGFloat)_heightForTreeArray:(NSArray *)treeArray {
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	
	CGFloat deltaY = initialDeltaY;
	for (NSTreeNode *currentNode in treeArray) {
		if ([[content childNodes] containsObject:currentNode] && [treeArray indexOfObjectIdenticalTo:currentNode] > 0) deltaY += _nodeSeparation;
		
		deltaY += _rowHeight;
	}
	
	return deltaY;
}

- (DisclosureButton *)_disclosureButtonForNode:(id)node {
	for (DisclosureButton *currentButton in _disclosureButtons)
		if (currentButton.representedObject == node)
			return currentButton;
	
	return nil;
}

- (DisclosureButton *)_newDisclosureButtonForNode:(id)node {
	DisclosureButton *disclosureButton = [DisclosureButton buttonWithRepresentedObject:node];
	
	[disclosureButton setTarget:self];
	[disclosureButton setAction:@selector(_disclosureButtonClicked:)];
	[disclosureButton sendActionOn:NSLeftMouseUpMask];
	
	return disclosureButton;
}

- (void)_disclosureButtonClicked:(DisclosureButton *)sender {
	[self _discloseNode:sender.representedObject];
}

@end

@implementation SourceList (PrivateDrawing)

- (NSSize)_resetFrameSize:(NSSize)size {
	if ([[self superview] respondsToSelector:@selector(documentVisibleRect)]) {
		NSTreeNode *content = [self valueForBinding:NSContentBinding];
		if (content == nil) return size;
				
		CGFloat height = [self _heightForTreeArray:[self _expandedNodesFromTree:content]];
		NSRect documentRect = [(NSClipView *)[self superview] documentVisibleRect];
		
		size.height = MAX(NSHeight(documentRect), height);
		size.width = NSWidth(documentRect);
    }
	
	return size;
}

- (CGFloat)_disclosureButtonSize {
	return (_rowHeight * (2.0/5.0));
}

- (CGFloat)_rowIndentForLevel:(NSUInteger)level {
	CGFloat indent = initialDeltaX, delta = (2.0/3.0) * _rowHeight;
	
	for (; level > 1; level--) {
		indent += delta;
		if ([_disclosureLevels containsObject:[NSNumber numberWithUnsignedInteger:level]]) indent += delta;
	}
	
	return indent;
}

@end

@implementation SourceList (PrivateSelection)

- (void)_discloseSelection:(NSInteger)direction {
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	NSIndexPath *selectionIndexPath = [self valueForBinding:AFSelectionIndexPathBinding];
	
	if (content == nil || selectionIndexPath == nil) return;
	NSTreeNode *node = [content descendantNodeAtIndexPath:selectionIndexPath];
	
	if ([_expandable containsObject:node]) {
		BOOL expanded = [_expanded containsObject:node];
		if (expanded ^ direction) [self _discloseNode:node];
	} else {
		NSTreeNode *parent = [node parentNode];
		if ([[content childNodes] containsObject:parent]) return;
		
		[self _selectNode:parent];
	}
}

- (void)_discloseNode:(NSTreeNode *)node {
	if ([_expanded containsObject:node]) {
		for (NSTreeNode *currentNode in [self _expandedNodesFromTree:node])
			[[self _disclosureButtonForNode:currentNode] setFrame:NSZeroRect];
		
		[_expanded removeObject:node];
	} else [_expanded addObject:node];
	
	[super setFrameSize:[self _resetFrameSize:_frame.size]];
	[self setNeedsDisplay:YES];
}

- (void)_selectNode:(NSTreeNode *)node {
	if (delegate != nil && !([delegate respondsToSelector:@selector(sourceList:canSelectObject:)] && [delegate sourceList:self canSelectObject:[node representedObject]])) {
		NSBeep();
		return;
	}
		
	[self setValue:[node indexPath] forBinding:AFSelectionIndexPathBinding];
	if ([_superview respondsToSelector:@selector(scrollToPoint:)]) [self _scrollToSelection];
}

- (void)_moveSelection:(NSInteger)direction {
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	
	NSIndexPath *selectionIndexPath = [self valueForBinding:AFSelectionIndexPathBinding];
	NSTreeNode *node = [content descendantNodeAtIndexPath:selectionIndexPath];
	
	NSMutableArray *treeArray = [self _expandedNodesFromTree:content];	
	[treeArray removeObjectsInArray:[content childNodes]];
	
	NSInteger newIndex = ([treeArray indexOfObjectIdenticalTo:node] + direction);	
	if (!AFArrayContainsIndex(treeArray, newIndex)) return;
	
	[self _selectNode:[treeArray objectAtIndex:newIndex]];
}

- (void)_scrollToSelection {
	NSTreeNode *_content = [self valueForBinding:NSContentBinding];
	
	NSMutableArray *contentArray = [self _expandedNodesFromTree:_content];
	
	NSTreeNode *selectedNode = [_content descendantNodeAtIndexPath:[self valueForBinding:AFSelectionIndexPathBinding]];
	
	NSUInteger selectedNodeIndex = [contentArray indexOfObjectIdenticalTo:selectedNode];
	if (selectedNodeIndex == NSNotFound) return;
	
	CGFloat deltaY = [self _heightForTreeArray:[contentArray subarrayWithRange:NSMakeRange(0, selectedNodeIndex)]];
	NSPoint scrollPoint = NSMakePoint(0, deltaY);
	
	NSRect visibleRect = [_superview documentVisibleRect];
	if (NSPointInRect(scrollPoint, visibleRect)) return;
	
	scrollPoint = [self convertPoint:scrollPoint toView:_superview];
	[_superview scrollToPoint:[_superview constrainScrollPoint:scrollPoint]];
	
	if ([[_superview superview] respondsToSelector:@selector(reflectScrolledClipView:)]) [[_superview superview] reflectScrolledClipView:_superview];
}

- (void)_expandNode:(NSTreeNode *)node {
	NSTreeNode *parent = [node parentNode];
	if (parent == nil || [_expanded containsObject:parent]) return;
	
	[_expanded addObject:parent];
	[self _expandNode:parent];
}

- (NSMutableArray *)_expandedNodesFromTree:(NSTreeNode *)node {
	NSTreeNode *content = [self valueForBinding:NSContentBinding];
	
	NSMutableArray *treeArray = [NSMutableArray array];
	[self _addExpandedNodes:content toCollection:treeArray];
	for (NSTreeNode *currentNode in [content childNodes]) if ([currentNode isLeaf]) [treeArray removeObjectIdenticalTo:currentNode];
	
	return treeArray;
}

- (void)_addExpandedNodes:(NSTreeNode *)node toCollection:(id)collection {
	for (NSTreeNode *currentNode in [node childNodes]) {
		[collection addObject:currentNode];
		if ([_expanded containsObject:currentNode]) [self _addExpandedNodes:currentNode toCollection:collection];
	}
}

@end
